package models;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

import com.hp.hpl.jena.query.Query;
import com.hp.hpl.jena.query.QueryExecution;
import com.hp.hpl.jena.query.QueryExecutionFactory;
import com.hp.hpl.jena.query.QueryFactory;
import com.hp.hpl.jena.query.QuerySolution;
import com.hp.hpl.jena.query.ResultSet;
import com.hp.hpl.jena.query.ResultSetFactory;
import com.hp.hpl.jena.query.ResultSetRewindable;
import com.hp.hpl.jena.rdf.model.InfModel;
import com.hp.hpl.jena.rdf.model.ModelFactory;
import com.hp.hpl.jena.util.FileManager;

/**
 * Class that implements the similarity search
 */
public class FindSimilar {
	static String queryPrefixes = "PREFIX dbpedia: <http://dbpedia.org/resource/>"
			+ "PREFIX dbp-ont: <http://dbpedia.org/ontology/>"
			+ "PREFIX owl: <http://www.w3.org/2002/07/owl#>"
			+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
			+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
			+ "PREFIX dc-term: <http://purl.org/dc/terms/>"
			+ "PREFIX foaf: <http://xmlns.com/foaf/0.1/>"
			+ "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>"
			+ "PREFIX skos: <http://www.w3.org/2004/02/skos/core#>";

	/**
	 * Method that returns a model with all the local RDF resources loaded
	 */
	private static com.hp.hpl.jena.rdf.model.Model loadDataSources() {
		com.hp.hpl.jena.rdf.model.Model myRawModel = (com.hp.hpl.jena.rdf.model.Model) FileManager
				.get().loadModel("dbpedia/yago_links_short.nt", "N3");

		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/geo_coordinates_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/article_categories_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/geo_coordinates_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/skos_categories_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/instance_types_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/labels_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/long_abstracts_en_short.nt", "N3"));
		((com.hp.hpl.jena.rdf.model.Model) myRawModel).add(FileManager.get()
				.loadModel("dbpedia/images_en-1_short.nt", "N3"));

		return myRawModel;
	}

	/**
	 * Similarity Search that returns a HashMap of similar items
	 * 
	 * @param currentCategory, the category that you want to find
	 * @param itemUriCount, counter of the occurences bei URI
	 * @param outlier, the attributes that find no additional monuments
	 */
	public static HashMap<String, Integer> getMonuments(String currentCategory,
			HashMap<String, Integer> itemUriCount, AtomicInteger outlier) {

		String queryString = queryPrefixes
				+ "SELECT DISTINCT ?monumentUri ?label ?category " + "WHERE { "
				+ " { " + "?monumentUri rdfs:label ?label . "
				+ "?monumentUri dc-term:subject ?category ." + " } "
				+ "FILTER regex(str(?category), \"" + currentCategory
				+ "\", \"i\") " + "} LIMIT 100";

		com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();

		InfModel infmodel = ModelFactory
				.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);

		Query query = QueryFactory.create(queryString);
		QueryExecution qe = QueryExecutionFactory.create(query, infmodel);
		ResultSetRewindable results = ResultSetFactory.makeRewindable(qe
				.execSelect());

		QuerySolution qs;

		if (results.size() <= 1) {
			query = QueryFactory
					.create(createQuery(similarityMethod2(currentCategory)));
			qe = QueryExecutionFactory.create(query, infmodel);
			results = ResultSetFactory.makeRewindable(qe.execSelect());
		}

		if (results.size() <= 1) {
			query = QueryFactory.create(createQuery(similarityMethod1(currentCategory)));
			qe = QueryExecutionFactory.create(query, infmodel);
			results = ResultSetFactory.makeRewindable(qe.execSelect());
		}

		if (results.size() <= 1) {
			outlier.incrementAndGet();
		}

		while (results.hasNext()) {
			qs = results.next();
			String uri = qs.get("monumentUri").toString();
			if (itemUriCount.containsKey(uri)) {
				int keys = itemUriCount.get(uri).intValue() + 1;
				itemUriCount.remove(uri);
				itemUriCount.put(uri, keys);
			} else {
				itemUriCount.put(uri, 1);
			}
		}

		return itemUriCount;
	}

	/**
	 * Search for all parts of the category without the numbers and connecting
	 * words
	 * 
	 * @param currentCategory, the category that you want to find
	 */
	public static ArrayList<String> similarityMethod2(String currentCategory) {
		String minResults = currentCategory;
		int numberOfResults = 9999;

		String[] testString = currentCategory.split("_|-");
		for (int i = 0; i < testString.length; i++) {
			if (testString[i].length() > 3 && !testString[i].matches(".*\\d.*")) {
				ArrayList<String> toAdd = new ArrayList();
				toAdd.add(testString[i]);
				String queryString = createQuery(toAdd);

				com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();

				InfModel infmodel = ModelFactory
						.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);

				Query query = QueryFactory.create(queryString);
				QueryExecution qe = QueryExecutionFactory.create(query,
						infmodel);
				ResultSetRewindable results = ResultSetFactory
						.makeRewindable(qe.execSelect());

				if (results.size() < numberOfResults) {
					numberOfResults = results.size();
					minResults = testString[i];
				}
			}
		}
		if (numberOfResults > 25) {
			minResults = currentCategory;
		}
		ArrayList<String> toAdd = new ArrayList();
		toAdd.add(minResults);
		return toAdd;
	}

	/**
	 * Search for each part of the category
	 * 
	 * @param currentCategory, the category that you want to find
	 */
	public static ArrayList<String> similarityMethod1(String currentCategory) {
		ArrayList<String> toAdd = new ArrayList();

		String[] testString = currentCategory.split("_|-");
		for (int i = 0; i < testString.length; i++) {
			if (testString[i].length() > 3 && !testString[i].matches(".*\\d.*")) {
				toAdd.add(testString[i]);
			}
		}
		return toAdd;
	}

	/**
	 * Automatically adds several filters
	 * 
	 * @param filterTerms, all terms that should be included in the category
	 */
	private static String createQuery(ArrayList<String> filterTerms) {
		String toReturn = queryPrefixes
				+ "SELECT DISTINCT ?monumentUri ?label ?category " + "WHERE { "
				+ " { " + "?monumentUri rdfs:label ?label . "
				+ "?monumentUri dc-term:subject ?category ." + " } ";
		for (int i = 0; i < filterTerms.size(); i++) {
			toReturn += "FILTER regex(str(?category), \"" + filterTerms.get(i)
					+ "\", \"i\") ";
		}
		toReturn += "} LIMIT 100";
		return toReturn;
	}

	/**
	 * Returns similar items and is called by the controller
	 * 
	 * @param categories, all selected categories that should be searched
	 * @param itemCount, the number of selected monuments
	 */
	public static List<Item> work(List<Attribute> categories, int itemCount) {
		HashMap<String, Integer> itemUriCount = new HashMap<String, Integer>();
		AtomicInteger outlier = new AtomicInteger(0);
		for (Attribute attribute : categories) {
			getMonuments(attribute.content.split(":")[2], itemUriCount, outlier);
		}

		List<Item> itemListNew = new ArrayList<Item>();
		Iterator<String> it = itemUriCount.keySet().iterator();

		double threshold = 0.8;
		while (true) {
			while (it.hasNext()) {
				String key = it.next();
				if (itemUriCount.get(key) > Math
						.floor((categories.size() - outlier.get()) * threshold)) {
					Item i = getItemDetails(key);
					if (i != null) {
						itemListNew.add(i);

					}
				}
			}
			if ((itemListNew.size() < itemCount + 2) && (threshold > 0.4)) {
				threshold -= 0.1;
				itemListNew = new ArrayList<Item>();
				it = itemUriCount.keySet().iterator();
			} else if (itemListNew.size() > itemCount || threshold < 0.31) {
				break;
			} else {
				threshold -= 0.1;
				itemListNew = new ArrayList<Item>();
				it = itemUriCount.keySet().iterator();
			}
		}

		List<Item> itemListNewOrdered = new ArrayList<Item>();
		// Order the items
		for (int i = 0; i < itemListNew.size(); i++) {
			int index = -1;
			for (int j = 0; j < itemListNewOrdered.size(); j++) {
				if (itemUriCount.get(itemListNew.get(i).uri) > itemUriCount
						.get(itemListNewOrdered.get(j).uri)) {
					index = j;
					break;
				}
			}
			if (index != -1) {
				itemListNewOrdered.add(index, itemListNew.get(i));
			} else {
				itemListNewOrdered.add(itemListNew.get(i));
			}
		}
		return itemListNewOrdered;
	}

	/** getItemDetails gets from the SPARQL endpoint the attributes needed
	 * in order to show an item's details
	 * @param uri uri of the item which's details have to be searched
	 */
	public static Item getItemDetails(String uri) {
		String queryString =

		"PREFIX dbpedia: <http://dbpedia.org/resource/>"
				+ "PREFIX dbp-ont: <http://dbpedia.org/ontology/>"
				+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>"
				+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>"
				+ "PREFIX dc-term: <http://purl.org/dc/terms/>"
				+ "PREFIX foaf: <http://xmlns.com/foaf/0.1/>"
				+ "PREFIX geo: <http://www.w3.org/2003/01/geo/wgs84_pos#>"
				+ "SELECT DISTINCT ?label ?abstract ?category ?type ?lat ?long ?thumb "
				+ "WHERE {" + "<"
				+ uri
				+ "> rdfs:label ?label ."
				+ "<"
				+ uri
				+ "> dbp-ont:abstract ?abstract ."
				+ "<"
				+ uri
				+ "> dc-term:subject ?category ."
				+ "<"
				+ uri
				+ "> rdf:type ?type ."
				+ "<"
				+ uri
				+ "> geo:lat ?lat ."
				+ "<"
				+ uri
				+ "> geo:long ?long ."
				+ "<"
				+ uri
				+ "> <http://dbpedia.org/ontology/thumbnail> ?thumb ."
				+ "FILTER langMatches(lang(?abstract), 'en')"
				+ "FILTER langMatches(lang(?label), 'en')" + "} LIMIT 100";

		Query query = QueryFactory.create(queryString);

		com.hp.hpl.jena.rdf.model.Model myRawModel = loadDataSources();
		InfModel infmodel = ModelFactory
				.createRDFSModel((com.hp.hpl.jena.rdf.model.Model) myRawModel);

		QueryExecution qe = QueryExecutionFactory.create(query, infmodel);

		return saveItemDetailsAttributes(uri, qe);
	}

	/**
	 * saveItemDetailsAttributes: save the attributes gotten in getItemDetails
	 * @param uri uri of the item for which the attributes have to be saved
	 * @param qe the query to execute
	 */
	private static Item saveItemDetailsAttributes(String uri, QueryExecution qe) {
		ResultSet results = qe.execSelect();

		List<Attribute> attributeList = new ArrayList<Attribute>();

		QuerySolution qs;
		Item item = null;

		if (Item.find("byUri", uri).fetch(1).isEmpty()) {
			item = new Item(uri, false);

			// Save single attributes
			if (results.hasNext()) {
				qs = results.next();
				attributeList.add(new Attribute(item, "label", qs.get("label")
						.toString()));
				attributeList.add(new Attribute(item, "abstract", qs.get(
						"abstract").toString()));
				attributeList.add(new Attribute(item, "lat", qs
						.get("lat")
						.toString()
						.substring(0,
								qs.get("lat").toString().lastIndexOf("^^"))));
				attributeList.add(new Attribute(item, "long", qs
						.get("long")
						.toString()
						.substring(0,
								qs.get("long").toString().lastIndexOf("^^"))));
				attributeList.add(new Attribute(item, "thumb", qs.get("thumb")
						.toString()));
			}

			// Save "category" attributes
			attributeList.addAll(saveSpecificAttribute(item, "category",
					results));

			// Save "type" attributes
			attributeList.addAll(saveSpecificAttribute(item, "type", results));

			item.attributeList.addAll(attributeList);
			if (item.attributeList.size() == 0) {
				item = null;
			} else {

				item.save();
			}
		} else {
			item = Item.find("byUri", uri).first();
		}
		return item;

	}

	/**
	 * saveSpecificAttribute: selects specific attributes from the results
	 * and saves them.
	 * @param item the item for which the attributes have to be saved
	 * @param attributeName the attribute that has to be saved
	 * @param results the results of the query
	 * @return returns an attribute list
	 */
	private static List<Attribute> saveSpecificAttribute(Item item,
			String attributeName, ResultSet results) {
		List<Attribute> attributeList = new ArrayList();
		QuerySolution qs;
		Attribute auxAttr;
		List<String> auxContent = new ArrayList();
		while (results.hasNext()) {
			qs = results.next();
			auxAttr = new Attribute(item, attributeName, qs.get(attributeName)
					.toString());
			if (!auxContent.contains(auxAttr.content)) {
				auxContent.add(auxAttr.content);
				attributeList.add(auxAttr);
			}
		}
		return attributeList;
	}
}
